﻿using System;
using System.Collections.Concurrent;
using System.Threading;

namespace Tool.PETimer 
{
    public class ThreadTimer : PETimer 
    {
        private readonly DateTime startDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
        private readonly ConcurrentDictionary<int, ThreadTask> taskDic= new ConcurrentDictionary<int, ThreadTask>();
        private const string tidLock = "TickTimer_tidLock";
        
        private readonly Thread timerThread;
        
        private readonly bool setHandle;
        private readonly ConcurrentQueue<ThreadTaskPack> packQue;

        /// <param name="interval">为0则是自己在update驱动，不为0则是新建线程驱动的驱动间隔</param>
        /// <param name="setHandle">为true则是在外部线程执行回调函数，为false则是就在此线程执行回调</param>
        public ThreadTimer(int checkInterval = 0, bool setHandle = true) 
        {
            this.setHandle = setHandle;
            if(setHandle)
                packQue = new ConcurrentQueue<ThreadTaskPack>();
            if(checkInterval != 0) 
            {
                timerThread = new Thread(StartTick);
                timerThread.Start();
            }
            
            void StartTick() 
            {
                try 
                {
                    while(true) 
                    {
                        UpdateCheck();
                        Thread.Sleep(checkInterval);
                    }
                }
                catch(ThreadAbortException e) 
                {
                    WarnFunc?.Invoke($"Tick Thread Abort:{e}.");
                }
            }
        }

        public override int AddTask(uint delay, Action<int> taskCB, Action<int> cancelCB, int count = 1) 
        {
            int tid = GenerateTid();
            double startTime = GetUTCMilliseconds();
            double destTime = startTime + delay;
            ThreadTask task = new ThreadTask(tid, delay, count, destTime, taskCB, cancelCB, startTime);
            if(taskDic.TryAdd(tid, task)) 
                return tid;
            else
            {
                WarnFunc?.Invoke($"key:{tid} already exist.");
                return -1;
            }
        }
        
        public override bool DeleteTask(int tid) 
        {
            if(taskDic.TryRemove(tid, out ThreadTask task)) 
            {
                if(setHandle && task.cancelCB != null) 
                    packQue.Enqueue(new ThreadTaskPack(tid, task.cancelCB));
                else 
                    task.cancelCB?.Invoke(tid);
                return true;
            }
            else 
            {
                WarnFunc?.Invoke($"tid:{tid} remove failed.");
                return false;
            }
        }
        
        public override void Reset() 
        {
            if(!packQue.IsEmpty)
                WarnFunc?.Invoke("Callback Queue is not Empty.");
            taskDic.Clear();
            timerThread?.Abort();
        }
        
        public void UpdateCheck() 
        {
            double nowTime = GetUTCMilliseconds();
            foreach(var task in taskDic.Values) 
            {
                if(nowTime < task.destTime) 
                    continue;

                ++task.loopIndex;
                if(task.count > 0) 
                {
                    --task.count;
                    if(task.count == 0) 
                        FinishTask(task.tid);
                    else 
                    {
                        task.destTime = task.startTime + task.delay * (task.loopIndex + 1);
                        CallTaskCB(task.tid, task.taskCB);
                    }
                }
                else //一直循环
                {
                    task.destTime = task.startTime + task.delay * (task.loopIndex + 1);
                    CallTaskCB(task.tid, task.taskCB);
                }
            }
        }
        void CallTaskCB(int tid, Action<int> taskCB) 
        {
            if(setHandle)
                packQue.Enqueue(new ThreadTaskPack(tid, taskCB));
            else 
                taskCB.Invoke(tid);
        }
        void FinishTask(int tid)
        {
            //线程安全字典，遍历过程中删除无影响。
            if(taskDic.TryRemove(tid, out ThreadTask task)) 
            {
                CallTaskCB(tid, task.taskCB);
                task.taskCB = null;
            }
            else 
            {
                WarnFunc?.Invoke($"Remove tid:{tid} task in Dic Failed.");
            }
        }
        
        public void HandleTask() 
        {
            while(packQue != null && packQue.Count > 0) 
            {
                if(packQue.TryDequeue(out ThreadTaskPack pack)) 
                    pack.cb.Invoke(pack.tid);
                else
                    ErrorFunc?.Invoke("packQue Dequeue Data Error.");
            }
        }

        private double GetUTCMilliseconds() 
        {
            TimeSpan ts = DateTime.UtcNow - startDateTime;
            return ts.TotalMilliseconds;
        }
        protected override int GenerateTid() 
        {
            lock(tidLock) 
            {
                while(true) 
                {
                    ++tid;
                    if(tid == int.MaxValue) 
                        tid = 0;
                    if(!taskDic.ContainsKey(tid)) 
                        return tid;
                }
            }
        }

        class ThreadTask
        {
            public int tid;
            public uint delay;
            public int count;
            public double destTime;//目标时间
            public Action<int> taskCB;
            public Action<int> cancelCB;

            public double startTime;
            public ulong loopIndex;

            public ThreadTask(int tid, uint delay, int count, 
                double destTime, Action<int> taskCB, Action<int> cancelCB, double startTime)
            {
                this.tid = tid;
                this.delay = delay;
                this.count = count;
                this.destTime = destTime;
                this.taskCB = taskCB;
                this.cancelCB = cancelCB;
                this.startTime = startTime;

                loopIndex = 0;
            }
        }
        
        class ThreadTaskPack 
        {
            public int tid;
            public Action<int> cb;
            public ThreadTaskPack(int tid, Action<int> cb) 
            {
                this.tid = tid;
                this.cb = cb;
            }
        }
    }
}